#    Copyright 2008, 2009 Tri-County Electric Cooperative, St. Matthews, South Carolina
#    This file is part of COOPGIS.
#    COOPGIS is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#    COOPGIS is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#    You should have received a copy of the GNU General Public License
#    along with COOPGIS.  If not, see <http://www.gnu.org/licenses/>.
#************************BEGIN Add Include Files to Path************************
import sys
NeededPaths=['/opt/coopgis/djangoproject/include', '/opt/coopgis/djangoproject']
for path in NeededPaths:
    if(not path in sys.path):
        sys.path.append(path)
#************************END   Add Include Files to Path************************
import application, cis, command, compatibleunit, geography, oms, symbology, unsorted
import COOPGIS_Ajax, COOPGIS_GPS, COOPGIS_Misc, COOPGIS_ReportLab, COOPGIS_String, COOPGIS_Update, COOPGIS_ReportLabStakerReport
import os, csv, shutil, datetime, time, reportlab, djangoproject.settings
from django.contrib.gis.db import models
from django.contrib.gis.geos import *
from django.http import HttpResponse
from django.contrib import admin
import _mysql as mysql

class StakerWorkOrder(models.Model):
    """StakerWorkOrder holds all Work Orders"""
    Name=models.CharField(max_length=250, unique=True)
    MultiPolygon=models.MultiPolygonField(db_column='the_geom', srid=djangoproject.settings.COOPGISSRID, null=True, blank=True)
    UserName=models.CharField(max_length=250)
    Description=models.TextField()
    MinimumX=models.FloatField(null=True, blank=True) #Hold Extents used for drawing an image of the Staking Work Order
    MinimumY=models.FloatField(null=True, blank=True)
    MaximumX=models.FloatField(null=True, blank=True)
    MaximumY=models.FloatField(null=True, blank=True)
    #The following fields are ones that appear at the top of the 1st page of a Staker Work Order
    HazardsAssociatedWithJobIndicator=models.BooleanField()
    WorkProcedureInvolvedIndicator=models.BooleanField()
    SpecialPrecautionsIndicator=models.BooleanField()
    EnergySourceControlsIndicator=models.BooleanField()
    PPERequirementsIndicator=models.BooleanField()
    TailgateDiscussionIndicator=models.BooleanField()
    CheckWhereSepticTankIsIndicator=models.BooleanField()
    WorkPlanReferenceIndicator=models.BooleanField()
    NewConstructionIndicator=models.BooleanField()
    SystemImprovementIndicator=models.BooleanField()
    ReplacementIndicator=models.BooleanField()
    Retire_No_ReplacementIndicator=models.BooleanField()
    GeneralFundsIndicator=models.BooleanField()
    No_OfServicesOH=models.IntegerField(null=True, blank=True)
    No_OfServicesUG=models.IntegerField(null=True, blank=True)
    Wire=models.CharField(max_length=250)
    LoanNo=models.CharField(max_length=250)
    Substation=models.CharField(max_length=250)
    MapNo=models.CharField(max_length=250)
    LocationNextTo=models.CharField(max_length=250)
    CalhounCountyIndicator=models.BooleanField()
    OrangeburgCountyIndicator=models.BooleanField()
    LexingtonCountyIndicator=models.BooleanField()
    RichlandCountyIndicator=models.BooleanField()
    KershawCountyIndicator=models.BooleanField()
    SumterCountyIndicator=models.BooleanField()
    WorkOrderNumber=models.CharField(max_length=250)
    W_P=models.CharField(max_length=250)
    N_W_P=models.CharField(max_length=250)
    EngineerStaked=models.CharField(null=True, blank=True, max_length=250)
    EngineerChecked=models.CharField(null=True, blank=True, max_length=250)
    EngineerReleasedForConst=models.CharField(null=True, blank=True, max_length=250)
    Co_PermitNo=models.CharField(null=True, blank=True, max_length=250)
    PUPS=models.CharField(null=True, blank=True, max_length=250)
    NewR_WIndicator=models.BooleanField()
    R_WToBeCutIndicator=models.BooleanField()
    R_WCutDate=models.CharField(null=True, blank=True, max_length=250)
    ExistingR_WIndicator=models.BooleanField()
    DisplayMapOutlineIndicator=models.NullBooleanField(blank=True, null=True)    
    #The Following Refer to Dates and the "Phase" of a Staker Work Order
    CreateDate=models.DateField()
    StakerPhaseStartIndicator=models.NullBooleanField(null=True, blank=True)
    StakerPhaseStartDate=models.DateField(null=True, blank=True)
    OutToConstructionStartIndicator=models.NullBooleanField(null=True, blank=True)
    OutToConstructionStartDate=models.DateField(null=True, blank=True)
    VerificationPhaseStartIndicator=models.NullBooleanField(null=True, blank=True)  #Verification can fix up the order
    VerificationPhaseStartDate=models.DateField(null=True, blank=True)
    ArchivedIndicator=models.NullBooleanField(null=True, blank=True)  #Once an Order is in archive status, no changes can be made
    ArchivedDate=models.DateField(null=True, blank=True)
    DeletedIndicator=models.NullBooleanField(null=True, blank=True)
    EstimateOnlyIndicator=models.NullBooleanField(db_column='estimateonlyindicator', null=True, blank=True)   
    ResidesOnServerIndicator=models.NullBooleanField(null=True, blank=True)
    CreateCommand=models.ForeignKey('Command', null=True, blank=True, on_delete=models.SET_NULL)
    AutomaticallyRecalculateExtentsIndicator=models.NullBooleanField(null=True, blank=True)
    ArchivedScannedWorkOrderFileName=models.ImageField(max_length=250, upload_to='stakerworkorderscanned')
    ArchivedInspectedWorkOrderFileName=models.ImageField(max_length=250, upload_to='stakerworkorderinspected')
    ArchivedGeneratedWorkOrderFileName=models.ImageField(max_length=250, upload_to='stakerworkordergenerated')
    StartStation=models.ForeignKey('Station', null=True, blank=True)
    StationListOverride=models.TextField(blank=True, null=True) #The 1st Station of StationListOverride is the Start Station
    StationSortByDistanceIndicator=models.NullBooleanField(null=True, blank=True)
    objects=models.GeoManager()
    class Meta:
        db_table=u'coopgisstakerworkorder'
        app_label='djangoproject'
    def GetOrCreate(self, Name, UserName, Description='', HazardsAssociatedWithJobIndicator=False, WorkProcedureInvolvedIndicator=False,
                    SpecialPrecautionsIndicator=False, EnergySourceControlsIndicator=False, PPERequirementsIndicator=False,  
                    WorkPlanReferenceIndicator=False, TailgateDiscussionIndicator=False, CheckWhereSepticTankIsIndicator=False, 
                    NewConstructionIndicator=False, SystemImprovementIndicator=False, ReplacementIndicator=False, Retire_No_ReplacementIndicator=False,
                    GeneralFundsIndicator=False, No_OfServicesOH=None, No_OfServicesUG=None, Wire='', LoanNo='', Substation='', 
                    MapNo='', LocationNextTo='', CalhounCountyIndicator=False, OrangeburgCountyIndicator=False, LexingtonCountyIndicator=False,
                    RichlandCountyIndicator=False, KershawCountyIndicator=False, SumterCountyIndicator=False, WorkOrderNumber='', W_P='',
                    N_W_P='', EngineerStaked='', EngineerChecked='', EngineerReleasedForConst='', Co_PermitNo='', PUPS='', 
                    NewR_WIndicator=False,  R_WToBeCutIndicator=False, R_WCutDate=None, ExistingR_WIndicator=False, CreateDate=None,
                    StakerPhaseStartIndicator=None, StakerPhaseStartDate=None, 
                    OutToConstructionStartIndicator=None, OutToConstructionStartDate=None,  
                    VerificationPhaseStartIndicator=None, VerificationPhaseStartDate=None,
                    ArchivedIndicator=None, ArchivedDate=None, DisplayMapOutlineIndicator=None,
                    DeletedIndicator=None, CreateCommand=None, MinimumX=None, MinimumY=None, MaximumX=None, MaximumY=None,
                    AutomaticallyRecalculateExtentsIndicator=None, StationListOverride=None, StationSortByDistanceIndicator=None, EstimateOnlyIndicator=False):  
                    #ArchivedScannedWorkOrderFileName cannot be set initially
                    #ArchivedGeneratedWorkOrderFileName also cannot be set initially
        try:
            New=StakerWorkOrder.objects.filter(Name__exact=Name)[0]
            return New
        except:
            New=StakerWorkOrder()
        New.Name=Name
        New.UserName=UserName
        New.Description=Description
        New.HazardsAssociatedWithJobIndicator=HazardsAssociatedWithJobIndicator
        New.WorkProcedureInvolvedIndicator=WorkProcedureInvolvedIndicator
        New.SpecialPrecautionsIndicator=SpecialPrecautionsIndicator
        New.EnergySourceControlsIndicator=EnergySourceControlsIndicator
        New.PPERequirementsIndicator=PPERequirementsIndicator
        New.TailgateDiscussionIndicator=TailgateDiscussionIndicator
        New.CheckWhereSepticTankIsIndicator=CheckWhereSepticTankIsIndicator
        New.WorkPlanReferenceIndicator=WorkPlanReferenceIndicator
        New.NewConstructionIndicator=NewConstructionIndicator
        New.SystemImprovementIndicator=SystemImprovementIndicator
        New.ReplacementIndicator=ReplacementIndicator
        New.Retire_No_ReplacementIndicator=Retire_No_ReplacementIndicator
        New.GeneralFundsIndicator=GeneralFundsIndicator
        New.No_OfServicesOH=No_OfServicesOH
        New.No_OfServicesUG=No_OfServicesUG
        New.Wire=Wire
        New.LoanNo=LoanNo
        New.Substation=Substation
        New.MapNo=MapNo
        New.LocationNextTo=LocationNextTo
        New.CalhounCountyIndicator=CalhounCountyIndicator
        New.OrangeburgCountyIndicator=OrangeburgCountyIndicator
        New.LexingtonCountyIndicator=LexingtonCountyIndicator
        New.RichlandCountyIndicator=RichlandCountyIndicator
        New.KershawCountyIndicator=KershawCountyIndicator
        New.SumterCountyIndicator=SumterCountyIndicator
        New.WorkOrderNumber=WorkOrderNumber
        New.W_P=W_P
        New.N_W_P=N_W_P
        New.EngineerStaked=EngineerStaked
        New.EngineerChecked=EngineerChecked
        New.EngineerReleasedForConst=EngineerReleasedForConst
        New.Co_PermitNo=Co_PermitNo
        New.PUPS=PUPS
        New.NewR_WIndicator=NewR_WIndicator
        New.R_WToBeCutIndicator=R_WToBeCutIndicator
        New.R_WCutDate=R_WCutDate
        New.ExistingR_WIndicator=ExistingR_WIndicator    
        New.EstimateOnlyIndicator=EstimateOnlyIndicator
        #The Following Refer to Dates and the "Phase" of a Staker Work Order
        if(not CreateDate):
            CreateDate=datetime.datetime.now()
        New.CreateDate=CreateDate
        New.StakerPhaseStartIndicator=StakerPhaseStartIndicator
        New.StakerPhaseStartDate=StakerPhaseStartDate
        New.OutToConstructionStartIndicator=OutToConstructionStartIndicator 
        New.OutToConstructionStartDate=OutToConstructionStartDate
        New.VerificationPhaseStartIndicator=VerificationPhaseStartIndicator 
        New.VerificationPhaseStartDate=VerificationPhaseStartDate
        New.ArchivedIndicator=ArchivedIndicator
        New.ArchivedDate=ArchivedDate
        New.DeletedIndicator=DeletedIndicator
        New.ResidesOnServerIndicator=djangoproject.settings.COOPGISServerIndicator
        New.CreateCommand=CreateCommand
        New.MinimumX=MinimumX
        New.MinimumY=MinimumY
        New.MaximumX=MaximumX
        New.MaximumY=MaximumY
        New.AutomaticallyRecalculateExtentsIndicator=AutomaticallyRecalculateExtentsIndicator
        New.StationListOverride=StationListOverride
        New.StationSortByDistanceIndicator=StationSortByDistanceIndicator
        New.DisplayMapOutlineIndicator=DisplayMapOutlineIndicator
        New.save()   
        return New            
    def __unicode__(self):        
        return self.Name
    def GetIDPhaseString(self):
        return str(self.id) + ':' + self.GetCurrentPhase()
    def GetStationsNeededForThisWorkOrder(self):
        Assemblies=compatibleunit.Assembly.objects.filter(StakerWorkOrder=self)
        Stations=[]
        for i in Assemblies:
            if(i.Station and (not i.Station in Stations)):
                Stations.append(i.Station)
            if(i.Span):
                if(not i.Span.StartStation in Stations):
                    Stations.append(i.Span.StartStation)
                if(not i.Span.EndStation in Stations):
                    Stations.append(i.Span.EndStation)
        return Stations
    def GetStationListOverride(self, Stations):
        PotentialStationids=str(self.StationListOverride).split(',')
        PotentialStations=[]
        SortedStationList=[]
        for i in PotentialStationids:
            try:
                PotentialStation=geography.Station.objects.filter(id=int(i))[0]
                if(PotentialStation in Stations):
                    SortedStationList.append(PotentialStation)
            except:
                pass   
        return SortedStationList
    def GetOrderedStationSpanList(self):
        """GetOrderedStationSpanList returns Stations in the proper order based on the StakerWorkOrder Attributes."""
        Stations = self.GetStationsNeededForThisWorkOrder()
        StationOverrides=self.GetStationListOverride(Stations)  #This List is sorted in Order
        if(self.StationSortByDistanceIndicator):
            OrderedStations=self.GetDistanceOrderedStationList(StationOverrides, Stations)
        else:
            OrderedStations=self.GetGeographicOrderedStationList(StationOverrides, Stations)
        StationSpanList=self.ConvertStationsToStationSpanList(OrderedStations)
        return StationSpanList
    def GetDistanceOrderedStationList(self, StationOverrides, Stations):
        if(StationOverrides):
            StartStation=StationOverrides[-1]
        elif(Stations):
            Stations.sort(lambda y1, y2: int(y1.Point.y-y2.Point.y)) #Sort the (Stations) in order of their Y Coordinate
            StartStation=Stations[-1]  #Pick the northern most one
        else:
            StartStation=None
        Result=[]
        if(StartStation):
            Stations.sort(key=lambda station: StartStation.Point.distance(station.Point))
            for station in StationOverrides:
                Result.append(station)
            for station in Stations:
                if(not station in Result):
                    Result.append(station)
        return Result
    def GetGeographicOrderedStationList(self, StationOverrides, Stations):
        if(StationOverrides):
            StartStation=StationOverrides[-1]
        elif(Stations):
            Stations.sort(lambda y1, y2: int(y1.Point.y-y2.Point.y)) #Sort the (Stations) in order of their Y Coordinate
            StartStation=Stations[-1]  #Pick the northern most one
        else:
            StartStation=None
        Result=[]
        if(StartStation):
            GeographicallyOrderedStationList=self.TraceStakerStations(StartStation, [], Stations)
            for station in Stations:  #Get any stations that were not connected by Spans to the start station
                if(not station in GeographicallyOrderedStationList):
                    GeographicallyOrderedStationList.append(station)
            for station in StationOverrides:
                Result.append(station)
            for station in GeographicallyOrderedStationList:
                if(not station in Result):
                    Result.append(station)
        return Result
    def ConvertStationsToStationSpanList(self, Stations):
        if(not Stations):
            return []
        StationSpanList=[]
        for station in Stations:
            StationSpanList.append([station])  #StationSpanList's format is [[Station1, Span1, Span2], [Station2, Span3], ...]
        StationsAlreadyVisited=[]
        for row in StationSpanList:
            PotentialSpans=row[0].GetNearbySpans()
            for span in PotentialSpans:
                if(span.StartStation in StationsAlreadyVisited or span.EndStation in StationsAlreadyVisited):
                    row.append(span)
            StationsAlreadyVisited.append(row[0])
        return StationSpanList
    def TraceStakerStations(self, CurrentStation, StationsAlreadyVisited, StationsToVisit):
        """TraceDownstream starts at the specified station and attempts to trace to all Stations that are part of the report"""
        StationsAlreadyVisited.append(CurrentStation)
        PotentialSpans=CurrentStation.GetNearbySpans()
        SortedPotentialSpans=[]  #The spans in order that they will be visited if there is anything at their far stations for this staker report
        for span in PotentialSpans:  #Follow All Span Guys without Conductors
            if(compatibleunit.Assembly.objects.filter(Span=span).filter(CompatibleUnit__Name='SPAN GUY')
               and not (compatibleunit.Assembly.objects.filter(Span=span).exclude(CompatibleUnit__Name='SPAN GUY'))):
                SortedPotentialSpans.append(span)
        for span in PotentialSpans:  #Follow all of the Secondary/Service
            if(compatibleunit.Assembly.objects.filter(Span=span).filter(ServiceIndicator=True)  
               or compatibleunit.Assembly.objects.filter(Span=span).filter(SecondaryIndicator=True)):
                SortedPotentialSpans.append(span)  
        for span in PotentialSpans:  #Then Follow all Taps
            if(span.TapIndicator):
                if(not span in SortedPotentialSpans):
                    SortedPotentialSpans.append(span)  
        for span in PotentialSpans:
            if(not span in SortedPotentialSpans):
                SortedPotentialSpans.append(span)
        for span in SortedPotentialSpans:
            if span.FarStation(CurrentStation) in StationsToVisit and not span.FarStation(CurrentStation) in StationsAlreadyVisited:     
                 if(not span.StakerStopTraceIndicator):          
                     self.TraceStakerStations(span.FarStation(CurrentStation), StationsAlreadyVisited, StationsToVisit) #Visit the station
        return StationsAlreadyVisited
    def CalculateExtents(self):
        Assemblies=compatibleunit.Assembly.objects.filter(StakerWorkOrder=self)
        Stations=[]
        for i in Assemblies:
            if(i.Station):
                if(not i.Station in Stations):
                    Stations.append(i.Station)
            else:
                if(not i.Span.StartStation in Stations):
                    Stations.append(i.Span.StartStation)
                if(not i.Span.EndStation in Stations):
                    Stations.append(i.Span.EndStation)
        DegreePoints=[]
        for i in Stations:
            DegreePoints.append(i.Point.transform(3857, True))
        XValues=[]
        YValues=[]
        for i in DegreePoints:
            XValues.append(i.x)
            YValues.append(i.y)
        XValues.sort()
        YValues.sort()
        if(XValues and YValues):
            MinimumX=XValues[0] - 150    #   Switched from degrees to SRID 3857, left comment if need to switch back    0.0005 is ~ 150 ft (assuming 1 degree is ~ 300,000 ft)
            MinimumY=YValues[0] - 150
            MaximumX=XValues[len(XValues)-1] + 150
            MaximumY=YValues[len(YValues)-1] + 150
        else: #Otherwise give some defaults that shows an arbitrary section of the map that is on the system
            MinimumX, MinimumY, MaximumX, MaximumY = -9107000, 3922000, -8865000, 4126000
        return MinimumX, MinimumY, MaximumX, MaximumY   
    def GenerateImage(self, MapFileName):
        ImageFileNameOption=' -o ' + self.GetImageFileName()
        MapFileNameOption = ' -m ' + MapFileName
        if(self.MinimumX and self.MinimumY and self.MaximumX and self.MaximumY):
            MinimumX = self.MinimumX
            MinimumY = self.MinimumY
            MaximumX = self.MaximumX
            MaximumY = self.MaximumY
        else:
            MinimumX, MinimumY, MaximumX, MaximumY = self.CalculateExtents()
        ExtentsOption = ' -e ' + str(MinimumX) + ' ' + str(MinimumY) + ' ' + str(MaximumX) + ' ' + str(MaximumY)
        os.system('sudo -u apachescript sudo /usr/bin/shp2img' + MapFileNameOption + ImageFileNameOption + ExtentsOption)
    def GetImageFileName(self):
        return djangoproject.settings.COOPGISTempImagePath + 'big' + str(self.id) + '.png'
    def GetReportFileName(self, Postfix=''):
        return djangoproject.settings.COOPGISStakerWorkOrderReportPath + str(self.id) + Postfix + '.pdf'
    def GetCurrentPhase(self):
        if(self.DeletedIndicator):
            return 'Delete'
        if(self.ArchivedIndicator):
            return 'Archive'
        if(self.VerificationPhaseStartIndicator):
            return 'Verification'
        if(self.OutToConstructionStartIndicator):
            return 'OutToConstruction'
        if(self.StakerPhaseStartIndicator):
            return 'Staker' 
    def ReportAddRemoveGetTableData(self):
        """Returns 3 Columns of Data in a list of lists, Primary/Secondary/Service Placeholder [empty string], CompatibleUnitName, Quantity"""
        AddAssemblies=compatibleunit.Assembly.objects.filter(StakerWorkOrder=self).filter(InstallStatus__Name='Install').exclude(CompatibleUnit__Name='SPAN GUY')
        AllAssembliesRecorded=[]
        AddAssembliesTotals={}  #Key = Compatible Unit, Value = Total
        for assembly in AddAssemblies:
            AllAssembliesRecorded.append(assembly)
            if(assembly.Station):
                try:
                    AddAssembliesTotals[str(assembly.CompatibleUnit)] = AddAssembliesTotals[str(assembly.CompatibleUnit)] + 1 
                except:
                    AddAssembliesTotals[str(assembly.CompatibleUnit)] = 1
            elif(assembly.Span):
                try:
                    AddAssembliesTotals[str(assembly.CompatibleUnit)] = AddAssembliesTotals[str(assembly.CompatibleUnit)] + round(GetConductorTotalLength([assembly], True),0)
                except KeyError:
                    AddAssembliesTotals[str(assembly.CompatibleUnit)] = round(GetConductorTotalLength([assembly], True),0)
        AddData=[]
        for i in AddAssembliesTotals:
            AddData.append(['', str(i), str(int(AddAssembliesTotals[i]))])
        RemoveAssemblies=compatibleunit.Assembly.objects.filter(StakerWorkOrder=self).filter(InstallStatus__Name='Remove').exclude(CompatibleUnit__Name='SPAN GUY')
        RemoveAssembliesTotals={}  #Key = Compatible Unit, Value = Total
        for assembly in RemoveAssemblies:
            AllAssembliesRecorded.append(assembly)
            if(assembly.Station):
                try:
                    RemoveAssembliesTotals[str(assembly.CompatibleUnit)] = RemoveAssembliesTotals[str(assembly.CompatibleUnit)] + 1 
                except:
                    RemoveAssembliesTotals[str(assembly.CompatibleUnit)] = 1
            elif(assembly.Span):
                try:
                    RemoveAssembliesTotals[str(assembly.CompatibleUnit)] = RemoveAssembliesTotals[str(assembly.CompatibleUnit)] + round(GetConductorTotalLength([assembly], True),0)
                except KeyError:
                    RemoveAssembliesTotals[str(assembly.CompatibleUnit)] = round(GetConductorTotalLength([assembly], True),0)
        RemoveData=[]
        for i in RemoveAssembliesTotals:
            RemoveData.append(['', str(i), str(int(RemoveAssembliesTotals[i]))])
        AddData.sort()
        RemoveData.sort()
        Data=[]
        for i in range(0, len(AddData)):  #Now merge the Add and Remove Lists
            try:
                Data.append(AddData[i] + [''] +  RemoveData[i])
            except:
                Data.append(AddData[i] + ['', '', '', ''])   #3 columns of Data, Spacer, 3 columns of Data
        for i in range(len(AddData), len(RemoveData)):  #Get any left over RemoveData rows
            Data.append(['', '', '', '', ] + RemoveData[i])
        return Data
    def ReportAddRemoveCreateTable(self):
        #First Define the Column Widths:
        WidthType=0.75*reportlab.lib.units.inch  #Primary, Secondary, Service
        WidthUnit=1*reportlab.lib.units.inch
    def ReportAddRemoveCreateTable(self):
        #First Define the Column Widths:
        WidthType=0.75*reportlab.lib.units.inch  #Primary, Secondary, Service
        WidthUnit=1*reportlab.lib.units.inch
        WidthSpanQuantity=1*reportlab.lib.units.inch
        WidthSpacer=2 * reportlab.lib.units.inch
        #There are two "columns" of the table going down each page
        colWidths=[WidthType, WidthUnit, WidthSpanQuantity, WidthSpacer, WidthType, WidthUnit, WidthSpanQuantity]
        HeadingRow=['    ', 'Unit', 'Quantity', '', '    ', 'Unit', 'Quantity']  
        data=self.ReportAddRemoveGetTableData()
        FullTable=[]
        FullTable.append(HeadingRow)
        for row in data:
            FullTable.append(row)
        t=reportlab.platypus.Table(FullTable, colWidths=colWidths)
        t.repeatRows=1
        t.setStyle(reportlab.platypus.TableStyle([
                                ('FONT',(0,0), (-1,0), 'Times-Bold', 10),#Heading Rows Font
                                ('FONT',(0,1), (-1,-1), 'Times-Roman', 8), #Rest of Table Font
                                ('TEXTCOLOR',(0,0),(-1,-1),reportlab.lib.colors.black),]))
        colWidths=[WidthType, WidthUnit, WidthSpanQuantity, WidthSpacer, WidthType, WidthUnit, WidthSpanQuantity]
        HeadingRow=['    ', 'Unit', 'Quantity', '', '    ', 'Unit', 'Quantity']  
        data=self.ReportAddRemoveGetTableData()
        FullTable=[]
        FullTable.append(HeadingRow)
        for row in data:
            FullTable.append(row)
        t=reportlab.platypus.Table(FullTable, colWidths=colWidths)
        t.repeatRows=1
        t.setStyle(reportlab.platypus.TableStyle([('FONT',(0,0), (-1,0), 'Times-Bold', 10),#Heading Rows Font
                                                  ('FONT',(0,1), (-1,-1), 'Times-Roman', 8), #Rest of Table Font
                                                  ('TEXTCOLOR',(0,0),(-1,-1),reportlab.lib.colors.black),]))
        return t
    def ReportAddRemoveFirstPage(self, canvas, doc):
        canvas.saveState()
        FirstPageInfo={}
        FirstPageInfo['PAGE_HEIGHT'] = reportlab.lib.pagesizes.LETTER[1]
        FirstPageInfo['PAGE_WIDTH'] = reportlab.lib.pagesizes.LETTER[0]
        canvas.setFont('Times-Bold', 16)
        canvas.drawString(3*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-.60*reportlab.lib.units.inch, "Add/Remove Report")
        canvas.setFont('Times-Roman', 8)
        canvas.drawString(0.4*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-.6*reportlab.lib.units.inch, str(datetime.datetime.today())[:10])
        canvas.setFont('Times-Roman', 11)
        canvas.drawString(.5*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-1*reportlab.lib.units.inch, "Tri-County Electric COOP")
        canvas.setFont('Times-Bold', 11)
        canvas.drawString(.5*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-1.25*reportlab.lib.units.inch, str(self.Name))
        canvas.setFont('Times-Roman', 11)
        canvas.drawString(.5*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-1.50*reportlab.lib.units.inch, "WO#:  " + str(self.WorkOrderNumber))

        canvas.setFont('Times-Roman', 8)
        canvas.drawString(5.25*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-1.70*reportlab.lib.units.inch, "Conductor Lengths are for individual wires.")

        canvas.setFont('Times-Bold', 11)
        canvas.drawString(.5*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-2*reportlab.lib.units.inch, "Add Units")
        canvas.setFont('Times-Roman', 11)
        canvas.drawString(.5*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-2.1*reportlab.lib.units.inch, "_____________________________________")
        canvas.setFont('Times-Bold', 11)
        canvas.drawString(5.25*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-2*reportlab.lib.units.inch, "Remove Units")
        canvas.setFont('Times-Roman', 11)
        canvas.drawString(5.25*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-2.1*reportlab.lib.units.inch, "_____________________________________")
        canvas.drawString(7.2 * reportlab.lib.units.inch, 10.5*reportlab.lib.units.inch, "Page %d of" % (doc.page))
        canvas.doForm('LastPageNumber')
        canvas.restoreState()
    def ReportAddRemoveLaterPages(self, canvas, doc):
        canvas.saveState()
        FirstPageInfo={}
        FirstPageInfo['PAGE_HEIGHT'] = reportlab.lib.pagesizes.LETTER[1]
        FirstPageInfo['PAGE_WIDTH'] = reportlab.lib.pagesizes.LETTER[0]
        canvas.drawString(.5*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-.5*reportlab.lib.units.inch, "Add Units")
        canvas.setFont('Times-Roman', 11)
        canvas.drawString(.5*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-.6*reportlab.lib.units.inch, "_____________________________________")
        canvas.setFont('Times-Bold', 11)
        canvas.drawString(5.25*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-.5*reportlab.lib.units.inch, "Remove Units")
        canvas.setFont('Times-Roman', 11)
        canvas.drawString(5.25*reportlab.lib.units.inch, FirstPageInfo['PAGE_HEIGHT']-.6*reportlab.lib.units.inch, "_____________________________________")
        canvas.drawString(7.2 * reportlab.lib.units.inch, 10.5*reportlab.lib.units.inch, "Page %d of" % (doc.page))
        canvas.doForm('LastPageNumber')
        canvas.restoreState()
    def ReportAddRemoveCreate(self):
        """Creates a PDF Report that contains a list of compatible units with quantities"""
        doc = reportlab.platypus.SimpleDocTemplate(self.GetReportFileName('AddRemove'), pagesize=reportlab.lib.pagesizes.LETTER)
        doc.pagesize=reportlab.lib.pagesizes.portrait(reportlab.lib.pagesizes.LETTER)
        doc.leftMargin = 0.25 * reportlab.lib.units.inch
        doc.rightMargin=0.25 * reportlab.lib.units.inch
        doc.topMargin=.5 * reportlab.lib.units.inch
        doc.bottomMargin=0.5 * reportlab.lib.units.inch
        Story=[reportlab.platypus.Spacer(.5, 2 * reportlab.lib.units.inch)]  
        styles=reportlab.lib.styles.getSampleStyleSheet()
        style=styles["Normal"]
        DataTable=self.ReportAddRemoveCreateTable()
        Story.append(DataTable)
        LastPageNumber = COOPGIS_ReportLab.LastPageNumberFlowable(8 * reportlab.lib.units.inch, 10.5*reportlab.lib.units.inch)
        Story.append(LastPageNumber)
        doc.build(Story, onFirstPage=self.ReportAddRemoveFirstPage, onLaterPages=self.ReportAddRemoveLaterPages)
    def GetIndexWhenReverseSorted(self):
        StakerWorkOrders=StakerWorkOrder.objects.all().order_by('-id')
        i = 0
        while(True):
            if(StakerWorkOrders[i] == self):
                return i
            i = i + 1
    def GetCommandidData(self):
        if(self.ResidesOnServerIndicator):
            return self.id, None
        return None, self.GetIndexWhenReverseSorted()
    def GetStakerSummary(self):
        Data={}
        OverHeadConductorAssemblies=compatibleunit.Assembly.objects.filter(StakerWorkOrder=self).filter(CompatibleUnit__ConductorIndicator=True).filter(CompatibleUnit__NormallyOverHeadIndicator=True)
        Assemblies=OverHeadConductorAssemblies.exclude(SecondaryIndicator=True).exclude(ServiceIndicator=True).filter(InstallStatus__Name='Install')
        Data['OverheadPrimaryWireFootageInstall']=GetConductorTotalLength(Assemblies, SpansOnly=True)
        Assemblies=OverHeadConductorAssemblies.filter(SecondaryIndicator=True).filter(InstallStatus__Name='Install')
        Data['OverheadSecondaryWireFootageInstall']=GetConductorTotalLength(Assemblies, SpansOnly=True)
        Assemblies=OverHeadConductorAssemblies.filter(ServiceIndicator=True).filter(InstallStatus__Name='Install')
        Data['OverheadServiceWireFootageInstall']=GetConductorTotalLength(Assemblies, SpansOnly=True)
        Assemblies=OverHeadConductorAssemblies.exclude(SecondaryIndicator=True).exclude(ServiceIndicator=True).filter(InstallStatus__Name='Remove')
        Data['OverheadPrimaryWireFootageRemove']=GetConductorTotalLength(Assemblies, SpansOnly=True)
        Assemblies=OverHeadConductorAssemblies.filter(SecondaryIndicator=True).filter(InstallStatus__Name='Remove')
        Data['OverheadSecondaryWireFootageRemove']=GetConductorTotalLength(Assemblies, SpansOnly=True)
        Assemblies=OverHeadConductorAssemblies.filter(ServiceIndicator=True).filter(InstallStatus__Name='Remove')
        Data['OverheadServiceWireFootageRemove']=GetConductorTotalLength(Assemblies, SpansOnly=True)
        Data['NumberOfOverheadConsumersInstall']=''
        Data['NumberOfOverheadConsumersRemove']=''
        UndergroundConductorAssemblies=compatibleunit.Assembly.objects.filter(StakerWorkOrder=self).filter(CompatibleUnit__ConductorIndicator=True).filter(CompatibleUnit__NormallyUndergroundIndicator=True)
        Assemblies=UndergroundConductorAssemblies.exclude(SecondaryIndicator=True).exclude(ServiceIndicator=True).filter(InstallStatus__Name='Install')
        Data['UndergroundPrimaryWireFootageInstall']=GetConductorTotalLength(Assemblies, SpansOnly=True)
        Assemblies=UndergroundConductorAssemblies.filter(SecondaryIndicator=True).filter(InstallStatus__Name='Install')
        Data['UndergroundSecondaryWireFootageInstall']=GetConductorTotalLength(Assemblies, SpansOnly=True)
        Assemblies=UndergroundConductorAssemblies.filter(ServiceIndicator=True).filter(InstallStatus__Name='Install')
        Data['UndergroundServiceWireFootageInstall']=GetConductorTotalLength(Assemblies, SpansOnly=True)
        Assemblies=UndergroundConductorAssemblies.exclude(SecondaryIndicator=True).exclude(ServiceIndicator=True).filter(InstallStatus__Name='Remove')
        Data['UndergroundPrimaryWireFootageRemove']=GetConductorTotalLength(Assemblies, SpansOnly=True)
        Assemblies=UndergroundConductorAssemblies.filter(SecondaryIndicator=True).filter(InstallStatus__Name='Remove')
        Data['UndergroundSecondaryWireFootageRemove']=GetConductorTotalLength(Assemblies, SpansOnly=True)
        Assemblies=UndergroundConductorAssemblies.filter(ServiceIndicator=True).filter(InstallStatus__Name='Remove')
        Data['UndergroundServiceWireFootageRemove']=GetConductorTotalLength(Assemblies, SpansOnly=True)
        Data['NumberOfUndergroundConsumersInstall']=''
        Data['NumberOfUndergroundConsumersRemove']=''      
        Data['NumberOfSecurityLightsInstall']=str(compatibleunit.Assembly.objects.filter(StakerWorkOrder=self).filter(CompatibleUnit__LightIndicator=True).filter(InstallStatus__Name='Install').count())
        Data['NumberOfSecurityLightsRemove']=str(compatibleunit.Assembly.objects.filter(StakerWorkOrder=self).filter(CompatibleUnit__LightIndicator=True).filter(InstallStatus__Name='Remove').count())
        Data['NumberOfTransformersInstall']=str(compatibleunit.Assembly.objects.filter(StakerWorkOrder=self).filter(CompatibleUnit__TransformerIndicator=True).filter(InstallStatus__Name='Install').count())
        Data['NumberOfTransformersRemove']=str(compatibleunit.Assembly.objects.filter(StakerWorkOrder=self).filter(CompatibleUnit__TransformerIndicator=True).filter(InstallStatus__Name='Remove').count())
        return Data
    def UpdatePolygon(self):
        if(self.ArchivedIndicator):
            return   #Do not update the polygon if archived because there may not be any assemblies associated with an Order at that point
        #Get a List of Stations, these will form the base points of the polygon (4 points per station, to give a squared off appearance)
        #Then get a List of Spans that Go in between these stations, they will be interior lines of the polygon that cannot be crossed
        Assemblies=compatibleunit.Assembly.objects.filter(StakerWorkOrder=self)
        Stations=[]
        Spans=[]
        for i in Assemblies:
            if(i.Station and not i.Station in Stations):
                Stations.append(i.Station)
            elif(i.Span and not i.Span in Spans):
                Spans.append(i.Span)
        #Now create a List of Polygons for each 
        Polygons=[]
        BoundingDistance=50 * djangoproject.settings.COOPGISDistanceMultiplier
        SpanStations = []
        for i in Spans:
            SpanStations.append(i.StartStation)
            SpanStations.append(i.EndStation)
        for i in Stations:
            if(not i in SpanStations):
                Temp=COOPGIS_Misc.GetBoundingPolygon(StartPoint=i.Point, EndPoint=i.Point, BoundingDistance=BoundingDistance)
                if(Temp and not Temp in Polygons):
                    Polygons.append(Temp)
        for i in Spans: 
            Temp=COOPGIS_Misc.GetBoundingPolygon(StartPoint=i.StartStation.Point, EndPoint=i.EndStation.Point, BoundingDistance=BoundingDistance)
            if(Temp and not Temp in Polygons):
                Polygons.append(Temp)
        if(Polygons):
            FinalPolygon=MultiPolygon(Polygons[0])
            for i in Polygons:
                if(not i == Polygons[0]):
                    FinalPolygon=FinalPolygon.union(i)
            try:
                FinalPolygon=MultiPolygon(FinalPolygon)
            except:
                FinalPolygon=None
        else:
            FinalPolygon=None
        self.MultiPolygon=FinalPolygon
        self.save()
    def GetCenterAndZoom(self):
        MinimumX, MinimumY, MaximumX, MaximumY = self.CalculateExtents()
        CenterX=(MinimumX+MaximumX)/2
        CenterY=(MinimumY+MaximumY)/2
        if(compatibleunit.Assembly.objects.filter(StakerWorkOrder=self)):
            Zoom=12
        else:
            Zoom=8
        return CenterX, CenterY, Zoom
    def GetRecipeReport(self):
        Assemblies=compatibleunit.Assembly.objects.filter(StakerWorkOrder=self)
        ValidRecipeReportList=[]
        MultipleRecipeReportList=[]
        NoRecipeReportList=[]
        IdentifierRecipeList=[]
        stakerRecipe=StakerRecipe()
        for assembly in Assemblies:
            if(assembly.StakerRecipeNumber):
                if(assembly.Station):
                    CurrentIdentifier=str(assembly.Station) + ' ' + str(assembly.StakerRecipeNumber)
                else:
                    CurrentIdentifier=str(assembly.Span) + ' ' + str(assembly.StakerRecipeNumber)
                if(not CurrentIdentifier in IdentifierRecipeList): 
                    temp=stakerRecipe.ApplyRecipe(assembly, 'testusername', TestOnly=True)
                    if(temp[:2] == 'No'):
                        NoRecipeReportList.append(temp)
                    elif(temp[:2] == 'Mu'):
                        MultipleRecipeReportList.append(temp)
                    else:
                        ValidRecipeReportList.append(temp)
                    IdentifierRecipeList.append(CurrentIdentifier)
        return '\n'.join(NoRecipeReportList) +'\n\n' + '\n'.join(MultipleRecipeReportList) + '\n\n' + ''.join(ValidRecipeReportList)

def GetConductorTotalLength(Assemblies, FloatResult=False, SpansOnly=False):
    Spans=[]
    Length = 0
    #FIXME Use assembly.CompatibleUnit.ConductorNumberOfWires and PhaseAIndicator, PhaseBIndicator, PhaseCIndicator, and PhaseNeutralIndicator
    #to calculate the number of feet of conductor
    for assembly in Assemblies:
        NumberOfPhases=len(assembly.GetPhaseCode(GetNeutral=True))
        if(NumberOfPhases == 0):
            NumberOfPhases=1
        if(assembly.CompatibleUnit.ConductorNumberOfWires):
            ConductorNumberOfWires=assembly.CompatibleUnit.ConductorNumberOfWires
        else:
            ConductorNumberOfWires=1
        if(ConductorNumberOfWires < 1):
            ConductorNumberOfWires=1
        Current = 0
        while(Current < NumberOfPhases):
            Spans.append(assembly.Span)
            Current = Current + ConductorNumberOfWires
            if(SpansOnly):  #Skip this loop (only perform calculations once) if we only care about the length of the spans
                Current = NumberOfPhases + 1
    if(SpansOnly):
        NewSpans = []
        for span in Spans:
            if(not span in NewSpans):
                NewSpans.append(span)
        Spans=NewSpans
    for span in Spans:
        Length = Length + round(span.Line.length, 0)
    if(FloatResult):
        return Length
    else:
        return str(round(Length, 0))[:-2]


class InstallStatus(models.Model):
    Name=models.CharField(max_length=250)
    Description=models.TextField()
    StakerReportOrder=models.IntegerField(null=True, blank=True)
    def __unicode__(self):
        return self.Name
    class Meta:
        db_table=u'coopgisinstallstatus'
        app_label='djangoproject'
    def GetOrCreate(self, Name, Description='', StakerReportOrder=1):
        try:
            New=InstallStatus.objects.get(Name=Name)
            return New
        except:
            pass
        New=InstallStatus()
        New.Name=Name
        New.Description=Description
        New.StakerReportOrder=StakerReportOrder
        New.save()
        return New
 


class StakerAssemblyHistory(models.Model):
    CompatibleUnit=models.ForeignKey('CompatibleUnit')
    Point=models.PointField(null=True, blank=True, srid=djangoproject.settings.COOPGISSRID)
    Line=models.LineStringField(null=True, blank=True, srid=djangoproject.settings.COOPGISSRID)
    StakerWorkOrder=models.ForeignKey('StakerWorkOrder')
    Date=models.DateField(null=True, blank=True)
    InstallIndicator=models.NullBooleanField(null=True, blank=True)
    RemoveIndicator=models.NullBooleanField(null=True, blank=True)
    objects=models.GeoManager()
    def __unicode__(self):
        return str(self.id) + ' ' + str(self.CompatibleUnit.Name)
    class Meta:
        db_table=u'coopgisstakerassemblyhistory'
        app_label='djangoproject'
    



class StakerRecipe(models.Model):
    """A Staker Recipe Installs and Removes Various Assemblies, with the Resulting Assemblies being the Results"""
    #Install refers to Install or Existing, combining assemblies will create the result
    Install1=models.ForeignKey('CompatibleUnit', null=True, blank=True, related_name="Install1")
    Install2=models.ForeignKey('CompatibleUnit', null=True, blank=True, related_name="Install2")
    Install3=models.ForeignKey('CompatibleUnit', null=True, blank=True, related_name="Install3")
    Install4=models.ForeignKey('CompatibleUnit', null=True, blank=True, related_name="Install4")
    #Remove refers to Remove Only, removing some assembly will create the result
    Remove1=models.ForeignKey('CompatibleUnit', null=True, blank=True, related_name="Remove1")
    Remove2=models.ForeignKey('CompatibleUnit', null=True, blank=True, related_name="Remove2")
    Remove3=models.ForeignKey('CompatibleUnit', null=True, blank=True, related_name="Remove3")
    Remove4=models.ForeignKey('CompatibleUnit', null=True, blank=True, related_name="Remove4")
    Result1=models.ForeignKey('CompatibleUnit', null=True, blank=True, related_name="Result1")
    Result2=models.ForeignKey('CompatibleUnit', null=True, blank=True, related_name="Result2")
    Result3=models.ForeignKey('CompatibleUnit', null=True, blank=True, related_name="Result3")
    Result4=models.ForeignKey('CompatibleUnit', null=True, blank=True, related_name="Result4")
    def __unicode__(self):
        InstallStringList=[]
        if(self.Install1):
            InstallStringList.append(str(self.Install1))
        if(self.Install2):
            InstallStringList.append(str(self.Install2))
        if(self.Install3):
            InstallStringList.append(str(self.Install3))
        if(self.Install4):
            InstallStringList.append(str(self.Install4))
        RemoveStringList=[]
        if(self.Remove1):
            RemoveStringList.append(str(self.Remove1))
        if(self.Remove2):
            RemoveStringList.append(str(self.Remove2))
        if(self.Remove3):
            RemoveStringList.append(str(self.Remove3))
        if(self.Remove4):
            RemoveStringList.append(str(self.Remove4))
        ResultStringList=[]
        if(self.Result1):
            ResultStringList.append(str(self.Result1))
        if(self.Result2):
            ResultStringList.append(str(self.Result2))
        if(self.Result3):
            ResultStringList.append(str(self.Result3))
        if(self.Result4):
            ResultStringList.append(str(self.Result4))

        FinalString=''
        if(InstallStringList):
            FinalString = FinalString + '   Install:  ' + ', '.join(InstallStringList)
        if(RemoveStringList):
            FinalString = FinalString + '   Remove:  ' + ', '.join(RemoveStringList)
        if(ResultStringList):
            FinalString = FinalString + '   Result:  ' + ', '.join(ResultStringList)
        return FinalString
    class Meta:
        db_table=u'coopgisstakerrecipe'
        app_label='djangoproject'
    def ApplyRecipe(self, assembly, UserName, TestOnly=False):
        """ApplyRecipe is run at the """
        if(not assembly.StakerRecipeNumber):
            return 'Did Not Apply Recipe'  #0 is the default Staker Recipe Number and is invalid...
        if(assembly.Station):
            Assemblies=compatibleunit.Assembly.objects.filter(Station=assembly.Station).filter(StakerRecipeNumber=assembly.StakerRecipeNumber)
            Message=' Station: ' + str(assembly.Station.id) + '   Recipe:  ' + str(assembly.StakerRecipeNumber)
            station=assembly.Station
            span=None 
        elif(assembly.Span):
            Assemblies=compatibleunit.Assembly.objects.filter(Span=assembly.Span).filter(StakerRecipeNumber=assembly.StakerRecipeNumber)
            Message=' Span: ' + str(assembly.Span.id) + '   Recipe:  ' + str(assembly.StakerRecipeNumber)  
            station=assembly.Station
            span=None
        CurrentRecipeList=[]
        FirstAssemblyIndicator=True
        AssemblyString=''
        for assembly in Assemblies:
            CurrentRecipeList=self.FindMatchingRecipes(assembly, CurrentRecipeList, FirstAssemblyIndicator)
            FirstAssemblyIndicator=False
            AssemblyString = AssemblyString + assembly.CompatibleUnit.Name + ', '
        for recipe in CurrentRecipeList:
            if(not self.AssemblyRecipeCountMatch(Assemblies, CurrentRecipeList)):
                CurrentRecipeList.remove(recipe)
        AssemblyString = AssemblyString[:-2]
        FinalRecipeList=self.MakeSureExactNumberOfAssemblies(Assemblies, CurrentRecipeList)
        if(not FinalRecipeList):
            if(TestOnly):
                return 'No Recipes Match:  ' + AssemblyString +  Message 
            else:
                raise(Exception('No Recipes Match Assemblies:  ' + AssemblyString  + Message))
        if(len(FinalRecipeList) > 1):
            if(TestOnly):
                return 'Multiple Recipes Match the Assemblies:  ' + AssemblyString + Message
            else:
                raise(Exception('Multiple Recipes Match the Assemblies:  ' + AssemblyString + Message))
        if(TestOnly):
            Message = Message + '\n' 
            for recipe in CurrentRecipeList:
                Message = Message + '     ' + str(recipe) + '\n'
            return('Valid Recipe for ' + Message)
        if(not djangoproject.settings.COOPGISServerIndicator):
            raise(Exception('Apply Recipe Can Only be run on the Server When Archiving a Staker Work Order'))
        self.CreateResult(FinalRecipeList[0], station, span, assembly, UserName)
        self.ConsumeIngredients(FinalRecipeList[0], Assemblies)
    def CreateResult(self, Recipe, station, span, assembly, UserName):
        Results=[]
        if(Recipe.Result1):
            Results.append(Recipe.Result1)
        if(Recipe.Result2):
            Results.append(Recipe.Result2)
        if(Recipe.Result3):
            Results.append(Recipe.Result3)
        if(Recipe.Result4):
            Results.append(Recipe.Result4)
        for result in Results:
            acommand=command.Command()
            if(station):
                try:
                    ManufacturerName=assembly.Manufacturer.Name
                except:
                    ManufacturerName=None
                try:
                    MeterFormName=assembly.MeterForm.Name
                except:
                    MeterFormName=None
                try:
                    MeterServiceTypeName=assembly.MeterServiceType.Name
                except:
                    MeterServiceTypeName=None
                acommand=acommand.Create(Name='CreateStationAssembly', AssemblyName=result.Name, StartPoint=station.Point, TagNumber=assembly.TagNumber, 
                                         PutIntoServiceDate=assembly.PutIntoServiceDate, PrimaryIndicator=assembly.PrimaryIndicator, 
                                         SecondaryIndicator=assembly.SecondaryIndicator, 
                                         PoleStubIndicator=assembly.PoleStubIndicator, PoleLightIndicator=assembly.PoleLightIndicator, 
                                         PoleGuyIndicator=False, 
                                         PoleMeterIndicator=assembly.PoleMeterIndicator, OwnerName=assembly.AssemblyOwner.Name, 
                                         ManufacturerName=ManufacturerName, 
                                         MeterFormName=MeterFormName, OpenIndicator=assembly.OpenIndicator,  
                                         MeterOnPoleIndicator=assembly.MeterOnPoleIndicator, PhaseAIndicator=assembly.PhaseAIndicator, 
                                         PhaseBIndicator=assembly.PhaseBIndicator,
                                         PhaseCIndicator=assembly.PhaseCIndicator, PhaseNeutralIndicator=assembly.PhaseNeutralIndicator, 
                                         CircuitNumber=assembly.CircuitNumber,
                                         SerialNumber=assembly.SerialNumber, MeterNumber=assembly.MeterNumber, MeterDCSINumber=assembly.MeterDCSINumber, 
                                         OldMapNumber=assembly.OldMapNumber,
                                         SubstationName=assembly.SubstationName, 
                                         TransformerCompletelySelfProtectedIndicator=assembly.TransformerCompletelySelfProtectedIndicator,
                                         ServiceIndicator=assembly.ServiceIndicator, MeterServiceTypeName=MeterServiceTypeName, 
                                         CommandUserName=UserName,
                                         CommandReadyToProcessIndicator=True, PhysicalAddress=assembly.PhysicalAddress, IdleIndicator=assembly.IdleIndicator, 
                                         InstallStatus='Existing', StakerWorkOrderid=None, 
                                         StakerWorkOrderIndexWhenReverseSorted=-1, StakerRecipeNumber=0)
            else:
                acommand=acommand.Create(Name='CreateSpanAssembly', AssemblyName=result.Name, StartPoint=span.StartStation.Point, 
                                        EndPoint=span.EndStation.point, Line=span.Line, 
                                        PhaseAIndicator=assembly.PhaseAIndicator, PhaseBIndicator=assembly.PhaseBIndicator, 
                                        PhaseCIndicator=assembly.PhaseCIndicator,
                                        PhaseNeutralIndicator=assembly.PhaseNeutralIndicator, CircuitNumber=assembly.CircuitNumber,  
                                        PrimaryIndicator=assembly.PrimaryIndicator, SecondaryIndicator=assembly.SecondaryIndicator,
                                        ServiceIndicator=assembly.ServiceIndicator,
                                        CommandUserName=UserName, CommandReadyToProcessIndicator=True, InstallStatus=InstallStatusName,
                                        IdleIndicator=False, StakerWorkOrderid=None, StakerWorkOrderIndexWhenReverseSorted=-1,
                                        StakerRecipeNumber=0)
            acommand.Process()
    def ConsumeIngredients(self, Recipe, Assemblies):
        for assembly in Assemblies:
            unit=assembly.CompatibleUnit
            if (unit == Recipe.Install1 or unit == Recipe.Install2 or unit == Recipe.Install3 or unit == Recipe.Install4 or
                unit == Recipe.Remove1 or unit == Recipe.Remove2 or unit == Recipe.Remove3 or unit == Recipe.Remove4):
                assembly.delete()
    def FindMatchingRecipes(self, assembly, CurrentRecipeList, FirstAssemblyIndicator):
        if(assembly.InstallStatus.Name == 'Install' or assembly.InstallStatus.Name == 'Existing'):
            Matching1=StakerRecipe.objects.filter(Install1=assembly.CompatibleUnit)
            Matching2=StakerRecipe.objects.filter(Install2=assembly.CompatibleUnit)
            Matching3=StakerRecipe.objects.filter(Install3=assembly.CompatibleUnit)
            Matching4=StakerRecipe.objects.filter(Install4=assembly.CompatibleUnit)
        elif(assembly.InstallStatus.Name == 'Remove'):
            Matching1=StakerRecipe.objects.filter(Remove1=assembly.CompatibleUnit)
            Matching2=StakerRecipe.objects.filter(Remove2=assembly.CompatibleUnit)
            Matching3=StakerRecipe.objects.filter(Remove3=assembly.CompatibleUnit)
            Matching4=StakerRecipe.objects.filter(Remove4=assembly.CompatibleUnit)
        if FirstAssemblyIndicator:
            for i in Matching1:
                CurrentRecipeList.append(i)
            for i in Matching2:
                CurrentRecipeList.append(i)
            for i in Matching3:
                CurrentRecipeList.append(i)
            for i in Matching4:
                CurrentRecipeList.append(i)
        else:
            for i in CurrentRecipeList:
                if(not (i in Matching1) and not (i in Matching2) and not (i in Matching3) and not (i in Matching4)):
                    CurrentRecipeList.remove(i)
        return CurrentRecipeList
    def MakeSureExactNumberOfAssemblies(self, Assemblies, CurrentRecipeList):
        RecipesThatHaveCorrectNumberOfAssemblies=[]
        for recipe in CurrentRecipeList:
            NeededInstallCompatibleUnits=recipe.GetNeededInstallCompatibleUnits()
            HaveInstallCompatibleUnits=[]
            for assembly in Assemblies:
                if(assembly.InstallStatus.Name == 'Install' or assembly.InstallStatus.Name == 'Existing'):
                    HaveInstallCompatibleUnits.append(assembly.CompatibleUnit)
            NeededRemoveCompatibleUnits=recipe.GetNeededRemoveCompatibleUnits()
            HaveRemoveCompatibleUnits=[]
            for assembly in Assemblies:
                if(assembly.InstallStatus.Name == 'Remove'):
                    HaveRemoveCompatibleUnits.append(assembly.CompatibleUnit)
            Match=True
            for i in HaveInstallCompatibleUnits:
                if(HaveInstallCompatibleUnits.count(i) <> NeededInstallCompatibleUnits.count(i)):
                    Match=False
            for i in HaveRemoveCompatibleUnits:
                if(HaveRemoveCompatibleUnits.count(i) <> NeededRemoveCompatibleUnits.count(i)):
                    Match=False
            if(Match):
                RecipesThatHaveCorrectNumberOfAssemblies.append(recipe)
        return RecipesThatHaveCorrectNumberOfAssemblies
    def GetNeededInstallCompatibleUnits(self):
        Needed=[]
        if(self.Install1):
            Needed.append(self.Install1)
        if(self.Install2):
            Needed.append(self.Install2)
        if(self.Install3):
            Needed.append(self.Install3)
        if(self.Install4): 
            Needed.append(self.Install4)
        return Needed
    def GetNeededRemoveCompatibleUnits(self):
        Needed=[]
        if(self.Remove1):
            Needed.append(self.Remove1)
        if(self.Remove2):
            Needed.append(self.Remove2)
        if(self.Remove3):
            Needed.append(self.Remove3)
        if(self.Remove4): 
            Needed.append(self.Remove4)
        return Needed
    def AssemblyRecipeCountMatch(self, Assemblies, CurrentRecipeList):
        AssemblyInstallDict={}
        AssemblyRemoveDict={}
        for assembly in Assemblies:
            if(AssemblyInstallDict.has_key(assembly.CompatibleUnit.Name) and (assembly.InstallStatus.Name == 'Existing' or 
               assembly.InstallStatus.Name == 'Install')):
                AssemblyInstallDict[assembly.CompatibleUnit.Name] = AssemblyInstallDict[assembly.CompatibleUnit.Name] + 1
            else:
                AssemblyInstallDict[assembly.CompatibleUnit.Name] = 1
            if(AssemblyRemoveDict.has_key(assembly.CompatibleUnit.Name) and (assembly.InstallStatus.Name == 'Remove')):
                AssemblyRemoveDict[assembly.CompatibleUnit.Name] = AssemblyRemoveDict[assembly.CompatibleUnit.Name] + 1
            else:
                AssemblyRemoveDict[assembly.CompatibleUnit.Name] = 1
        NeededInstallCompatibleUnits=self.GetNeededInstallCompatibleUnits()
        NeededRemoveCompatibleUnits=self.GetNeededRemoveCompatibleUnits()  
        for installAssembly in NeededInstallCompatibleUnits:
            try:
                if(NeededInstallCompatibleUnits.count(installAssembly) != AssemblyInstallDict[installAssembly.CompatibleUnit.Name]):
                    return False
            except KeyError:
                return False
        for removeAssembly in NeededRemoveCompatibleUnits:
            try:
                if(NeededRemoveCompatibleUnits.count(installAssembly) != AssemblyRemoveDict[removeAssembly.CompatibleUnit.Name]):
                    return False
            except KeyError:
                return False
        return True
                
    
class StakerQuickGeneratePolygon(models.Model):
    Polygon=models.PolygonField(db_column='the_geom', verbose_name=('polygon'), srid=djangoproject.settings.COOPGISSRID, null=True, blank=True)
    StakerWorkOrder=models.ForeignKey('StakerWorkOrder', editable=False)
    objects=models.GeoManager()
    def __unicode__(self):
        try:
            return 'Quick Generated Polygon for Staker Work Order' + str(self.StakerWorkOrder.id) + '  ' + str(self.StakerWorkOrder.Name)
        except:
            return 'Invalid Quick Generated Polygon (no/invalid Staker Work Order id)'
    class Meta:
        db_table=u'coopgisstakerquickgeneratepolygon'
        app_label='djangoproject'

from django import forms
from django.contrib.gis import admin
class StakerQuickGeneratePolygonAdmin(admin.GeoModelAdmin):
    list_filter=('polygon',)
    list_display=('object', 'polygon')


try:
    admin.site.register(StakerRecipe)
except:
    pass


            

